Skip to content

fix(screening): address code-review findings on Rentometer fallback chain - #378

Merged
paruff merged 2 commits into
mainfrom
fix/rentometer-review-findings
Aug 21, 2026
Merged

fix(screening): address code-review findings on Rentometer fallback chain#378
paruff merged 2 commits into
mainfrom
fix/rentometer-review-findings

Conversation

@paruff

@paruff paruff commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

A multi-agent code review of #376 (Rentometer API adapter) surfaced 9 findings. This fixes all of them:

  • Rentometer never fired for HUD/USDA screenings_PipelineView (the adapter used for those two source types) never carried address/city/state, so the guard in _get_monthly_rent() always failed and the feature's whole point (replacing HUD FMR with real comps) silently didn't apply to half the source types. _adapt_source_to_pipeline() now populates them.
  • Swallowed AttributeError could discard a fetched rent — the DB-write cache-on-read assumed pipeline_property.pk always exists, which _PipelineView doesn't have. Added a _cache_rent() helper that only writes when a pk is present.
  • screening_preview()'s "without saving" contract was silently violated_get_monthly_rent() wrote to the DB as a side effect even in preview mode. screen_property()/eval helpers now take a cache_rent flag; the preview view passes cache_rent=False.
  • Silent except Exception: pass — both catch blocks in _get_monthly_rent() logged nothing, making a real bug indistinguishable from "no data" (violates AGENTS.md rule Set up Copilot instructions #10). Now logged. RentometerClient._get() also classifies timeouts/5xx/malformed JSON into RentometerError instead of leaking raw requests exceptions past every catch point.
  • ZIP-extraction regex could match a house number_extract_zip()'s fallback grabbed the first 5-digit token in an address; anchored to the end of the string instead.
  • Hand-rolled unbounded cacherentometer.py's module-level _rent_cache dict is replaced with django.core.cache, matching the existing pattern in walkscore.py/schools.py in the same package (fixes both the multi-worker cache-miss problem and unbounded memory growth).
  • _parse_rent() float round-trip — now goes straight to Decimal instead of through binary float.
  • Stale docstringbedrooms param said "future lookups" but is already used today for the cache key.

Changes

  • core/services/screening.py
  • core/integrations/market/rentometer.py
  • core/views/__init__.py (screening_preview passes cache_rent=False)
  • tests/test_rentometer.py, tests/e2e/test_rentometer_e2e.py (new coverage)

Test plan

  • New tests: fallback chain (Rentometer success, Rentometer-fails→HUD-succeeds, cache_rent=False, HUD/USDA _PipelineView doesn't crash), ZIP/house-number regex regression, _parse_rent edge cases, RentometerClient._get() error classification
  • ruff check / ruff format --check / mypy clean
  • Full existing suite + new tests: 913 passed, 1 skipped (4 failures are pre-existing, unrelated ATTOM live-API 401s — missing test credentials in this environment, not caused by this change)

🤖 Generated with Claude Code

…hain

Multi-agent review of #376 (Rentometer adapter) surfaced 9 issues,
fixed here:

- Rentometer's address-based lookup could never fire for HUD/USDA
  sources: _PipelineView (the adapter used for those sources) never
  carried address/city/state, so the whole feature silently degraded
  to HUD FMR for 2 of 4 property source types. _adapt_source_to_pipeline
  now populates them from the source model.

- The DB-write cache-on-read in _get_monthly_rent assumed
  pipeline_property always has a `.pk`, which _PipelineView (no
  persisted row) doesn't have — an AttributeError there was silently
  swallowed, discarding a successfully fetched rent. Added a
  _cache_rent() helper that only writes when a pk exists.

- That same DB write also silently violated screening_preview()'s
  documented "without saving" contract. screen_property() and the
  eval helpers now take a cache_rent flag; screening_preview() passes
  cache_rent=False.

- Both `except Exception: pass` blocks in _get_monthly_rent logged
  nothing, making a real bug (schema drift, broken import) look
  identical to "no data available" (AGENTS.md rule #10). Now logged,
  and RentometerClient._get() classifies timeouts/5xx/malformed JSON
  into RentometerError instead of leaking raw requests exceptions.

- _extract_zip's ZIP regex matched the first 5-digit token in an
  address, which could be a house number. Anchored to the end of the
  string instead.

- rentometer.py's hand-rolled, unbounded _rent_cache dict is replaced
  with django.core.cache (matching walkscore.py/schools.py in the same
  package), fixing both the multi-worker cache-miss problem and the
  memory-growth issue.

- _parse_rent no longer round-trips through binary float before
  producing a Decimal.

- Fixed a stale "future lookups" docstring on the bedrooms param (it's
  used today, for the cache key).

Added tests for the fallback chain (Rentometer success, Rentometer-
fails-then-HUD-succeeds, cache_rent=False, HUD/USDA _PipelineView
non-crash), the ZIP/house-number regex regression, _parse_rent edge
cases, and RentometerClient._get()'s error classification. Full
existing suite plus new tests: 913 passed (4 pre-existing, unrelated
ATTOM live-API failures — missing test credentials).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread core/integrations/market/rentometer.py Fixed
Comment thread core/services/screening.py Fixed
Comment thread core/services/screening.py Fixed
CI on this branch caught three issues in the previous commit's fixes:

- _cache_rent() referenced PipelineProperty.objects.filter(...) but
  PipelineProperty is only imported under `if TYPE_CHECKING:` in this
  module — a NameError on every real invocation, silently swallowed by
  the (now-logged) except block. This bug predates this branch: the
  original inline code had the same gap, which is exactly why the
  previous commit's new fallback-chain tests (the first to actually
  exercise a successful rent lookup) caught it. Added a local
  `from core.models import PipelineProperty` inside _cache_rent, matching
  this file's existing lazy-import convention for model access.

- CodeQL flagged the new cache-key hash (MD5 over address/city/state/
  zip) and the new log statements (interpolating the raw zip_code) as
  PII handling issues. Switched the cache key to SHA-256, and swapped
  the logged zip_code for pipeline_property's pk (an opaque internal
  id, not location data).

Verified: the two previously-failing E2E tests now pass, full
rentometer/screening suite (61 tests) green, ruff/mypy clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@paruff
paruff merged commit 903b567 into main Aug 21, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants